<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <!-- 基本语法 -->
    <script>
        let s1 = Symbol('foo');
        let s2 = Symbol('bar');
        let s3 = Symbol('bar');
        console.log(s1, s2, s3 === s2);
        let s = {
            [s1]: 'foo',
            [s3]: 'bar',
            s1: 'test'
        };
        s[s2] = 'bar';
        s1 = Symbol('change');
        console.log(s, s1.description);

        /* Object.getOwnPropertySymbols() - 返回Symbol键名 */
        console.log(Object.getOwnPropertySymbols(s));
        /* Reflect.ownKeys() - 返回所有类型键名 */
        console.log(Reflect.ownKeys(s));

    </script>

    <!-- Symbol.for(), Symbol.keyFor() -->
    <script>
        // Symbol.for() - 在全局符号注册表中创建并重用符号
        // 在函数内部运行生成的Symbol值也登记在全局环境
        {
            let s1 = Symbol.for('foo');
            let s2 = Symbol.for('foo');
            let s3 = Symbol('foo');
            let s4 = Symbol('foo');
            console.log(s1 === s2, s3 === s4);
            // 在全局中登记的Symbol, Symbol.keyFor()才能返回名字, 否则返回undefined
            console.log(Symbol.keyFor(s1), Symbol.keyFor(s4));
        }
    </script>

    <!-- 内置的Symbol值 -->
    <script>
        /* Symbol.hasInstance */
        // 与instanceof相似, 确定一个对象实例的原型链上是否有原型
        function Foo() { }
        const foo = new Foo();
        console.log(foo instanceof Foo);
        console.log(Foo[Symbol.hasInstance](foo));

        /* Symbol.isConcatSpreadable */
        // 表示该对象用于Array.prototype.concat()时，是否可以展开
        // 用于数组时默认为undefined, 可以展开, true也有展开的效果
        // 用于类数组对象时默认false, 不展开
        const arr = [0, 1, 2];
        const arr1 = arr.concat(3, 4, 5);
        console.log(arr[Symbol.isConcatSpreadable]);
        arr[Symbol.isConcatSpreadable] = false;
        console.log(arr.concat(3, 4, 5));
        let obj = { length: 2, 0: 'c', 1: 'd' };
        ['a', 'b'].concat(obj, 'e') // ['a', 'b', obj, 'e']
        obj[Symbol.isConcatSpreadable] = true;
        ['a', 'b'].concat(obj, 'e') // ['a', 'b', 'c', 'd', 'e']

        /* Symbol.species */
        // 指向一个构造函数。创建衍生对象时，会使用该属性
        // 子类使用（来自基类的）继承的方法时, 希望返回基类的实例, 而不是子类的实例
        class Bar extends Array { }
        class Baz extends Array {
            static get [Symbol.species]() {
                return Array;
            }
        }
        let bar = new Bar();
        let baz = new Baz();
        console.log(bar instanceof Bar, baz instanceof Baz, bar instanceof Array, baz instanceof Array);
        bar = bar.concat();
        baz = baz.concat();
        console.log(bar instanceof Bar, baz instanceof Baz, bar instanceof Array, baz instanceof Array);

        /* Symbol.match */
        // 正则表达式匹配字符串, 传入非正则表达式值则将改值转换为正则对象
        class StringMatcher {
            constructor(str) {
                this.str = str;
            }
            [Symbol.match](target) {
                return target.includes(this.str);
            }
        }
        console.log('fobar'.match(new StringMatcher('foo')));

        /* Symbol.replace */
        class StringReplacer {
            constructor(str, restr) {
                this.str = str;
                this.restr = restr;
            }
            [Symbol.replace](target) {
                return target.replace(this.str, this.restr);
            }
        }
        console.log('foobar'.replace(new StringReplacer('foo', 'bar')));

        /* Symbol.search */
        class StringSearcher {
            constructor(str) {
                this.str = str;
            }
            [Symbol.search](target) {
                return target.search(this.str);
            }
        }
        console.log('foobar'.search(new StringSearcher('bar')));

        /* Symbol.split */
        class StringSpliter {
            constructor(str) {
                this.str = str;
            }
            [Symbol.split](target) {
                return target.split(this.str);
            }
        }
        console.log('bazfoobar'.split(new StringSpliter('foo')));

        /* Symbol.toPrimitive */
        // 根据参数类型控制返回的原始值
        let topr = {
            [Symbol.toPrimitive](hint) {
                switch (hint) {
                    case 'number':
                        return 123;
                    case 'string':
                        return 'str';
                    case 'default':
                        return 'default';
                    default:
                        throw new Error();
                }
            }
        };
        console.log(Number(topr), (9 + topr), String(topr));
    
        /* Symbol.toStringTag */
        // 修改实例标识符
        class Tst {
            constructor(){
                this[Symbol.toStringTag] = 'Tst';
            }
        }
        let tst = new Tst();
        console.log(tst.toString());

        /* Symbol.unscopables */
        // 不推荐使用
    </script>
</body>

</html>